﻿using PersistentObject.Common;
using PersistentObject.Common.Schema;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PersistentObject
{
    public class ObjectLoader 
    {
        public IDataReader DataReader { get; private set; }

        public BaseDialect Dialect { get; private set; }

        public int Deep { get; private set; }

        Dictionary<string, object> HashData = new Dictionary<string, object>();

        Dictionary<string, int> SequenceDict = new Dictionary<string,int>();

        public ObjectLoader(IDataReader dataReader, BaseDialect dialect, int deep)
        {
            DataReader = dataReader;
            Dialect = dialect;
            Deep = deep;
        }

        Dictionary<Type, Dictionary<object, object>> LoadedDict = new Dictionary<Type, Dictionary<object, object>>();
        public IEnumerable<T> LoadObjects<T>()
        {
            LoadedDict.Clear();
            List<T> list = new List<T>();
            while (DataReader.Read())
            {
                CreateHash();
                T obj = CreateObject<T>();
                list.Add(obj);
            }
            return list;
        }

        private void CreateHash()
        {
            HashData.Clear();
            SequenceDict.Clear();
            int colCount = DataReader.FieldCount;
            for (int i = 0; i < colCount; i++)
            {
                string header = DataReader.GetName(i);
                object value = DataReader.GetValue(i);
                PutHeader(header);
                int sequence = SequenceDict[header];
                var hashKey = Dialect.NamingPattern.GetColumnHashKey(header, sequence);
                HashData.Add(hashKey, value);
            }
        }

        private void PutHeader(string header)
        {
            if (!SequenceDict.ContainsKey(header))
            {
                SequenceDict.Add(header, 0);
            }
            else
            {
                SequenceDict[header]++;
            }
        }

        T CreateObject<T>()
        {
            Table table = Dialect.SchemaHelper.CreateTable<T>(Deep);
            object obj = CreateObject(table);
            return (T)obj;
        }

        object CreateObject(Table table)
        {
            object keyValue = GetColumnValue(table.KeyColumn);
            if (keyValue == null) return null;

            if(LoadedDict.ContainsKey(table.Type))
            {
                if(LoadedDict[table.Type].ContainsKey(keyValue))
                {
                    return LoadedDict[table.Type][keyValue];
                }
            }
            else
            {
                LoadedDict.Add(table.Type, new Dictionary<object, object>());
            }

            //check key value
            keyValue = Dialect.DataPattern.FormatValue(table.KeyColumn, keyValue);

            object obj = Activator.CreateInstance(table.Type);
            table.KeyColumn.Property.SetValue(obj, keyValue);
            LoadedDict[table.Type].Add(keyValue, obj);

            foreach (var column in table.ColumnList)
            {
                if (column.IsForeignKeyColumn)
                {
                    var foreignKey = GetColumnValue(column);
                    if (foreignKey != null)
                    {
                        var foreignObject = CreateObject(column.ForeignTable);
                        column.Property.SetValue(obj, foreignObject);
                    }
                }
                else
                {
                    var value = GetColumnValue(column);
                    value = Dialect.DataPattern.FormatValue(column, value);
                    column.Property.SetValue(obj, value);
                }
            }

            return obj;
        }

        object GetColumnValue(Column column)
        {
            string fullHeader = Dialect.NamingPattern.GetFullHashKey(column);
            string simpleHeader = Dialect.NamingPattern.GetSimpleHashKey(column);
            object fullValue = HashData.ContainsKey(fullHeader) ? HashData[fullHeader] : null;
            object simpleValue = HashData.ContainsKey(simpleHeader) ? HashData[simpleHeader] : null;
            var value = fullValue != null ? fullValue : simpleValue;
            if (value == DBNull.Value)
            {
                value = null;
            }
            return value;
        }
    }
}
